import json
import math
import os

import pandas as pd
import torch
from pathlib import Path
import logging
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

from sklearn.metrics import confusion_matrix, classification_report


def try_gpu(i=0):
    if torch.cuda.device_count() >= i + 1:
        return torch.device('cuda:' + str(i))
    else:
        return torch.device('cpu')

def get_test_csv_path(no):
    return f'../logs/{no}/test.csv'
def get_save_dir(no):
    return f'../checkpoints/{no}'
def get_best_file(no):
    return os.path.join(get_save_dir(no), 'best.pth')
def get_checkpoint_file(no):
    return os.path.join(get_save_dir(no), 'checkpoint.pth')
def check_and_create_path(path):
    path = Path(path)
    path.parent.mkdir(parents=True, exist_ok=True)
    path.touch(exist_ok=True)


def get_logger(no=None):
    if no is None:
        log_path = None
    else:
        log_path = f'../logs/{no}/log.txt'
    
    logger = MyLoggerV1.create(log_path)
    return logger


class MyLoggerV1():
    """单例Logger类"""
    logger = None
    log_path = None
    
    @classmethod
    def create(cls, log_path):
        if cls.logger is None:
            cls.log_path = log_path
            check_and_create_path(log_path)
            
            cls.logger = logging.getLogger(__name__)
            cls.logger.setLevel(logging.DEBUG)  # 设置日志级别
            
            # 创建一个handler，用于写入日志文件
            file_handler = logging.FileHandler(log_path, encoding='utf-8')
            file_handler.setLevel(logging.DEBUG)
            
            # 创建一个handler，用于将日志输出到控制台
            stream_handler = logging.StreamHandler()
            stream_handler.setLevel(logging.DEBUG)
            
            # 定义handler的输出格式
            formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
            file_handler.setFormatter(formatter)
            stream_handler.setFormatter(formatter)
            
            # 给logger添加handler
            cls.logger.addHandler(file_handler)
            cls.logger.addHandler(stream_handler)
        
        elif log_path is not None:
            assert cls.log_path == log_path
        
        return cls.logger
    
    @classmethod
    def get_logger(cls):
        """返回配置好的logger对象"""
        if cls.logger is None:
            raise ValueError("Logger has not been created. Call 'create' method first.")
        return cls.logger


def metrics_function_v1(preds, targets):
    metrics = MyMetricsV1(preds, targets)

    return metrics

class MyMetricsV1():
    def __init__(self, preds, targets):
        self.cm = confusion_matrix(targets, preds)
        self.TP = self.cm[0, 0]
        self.FN = self.cm[0, 1]
        self.FP = self.cm[1, 0]
        self.TN = self.cm[1, 1]
        
        self.cr = classification_report(targets, preds, digits=4)
        
        self.MCC = (self.TP * self.TN - self.FP * self.FN) / math.sqrt(
            (self.TP + self.FP) * (self.TP + self.FN) * (self.TN + self.FP) * (self.TN + self.FN))
        
        N_normal = targets.count(0)
        N_fault = len(targets) - N_normal
        # 不知alpha与beta的含义分别是什么，即不知道alpha是正样本的比例还是负样本的比例，只能分别试一下
        alpha = N_fault / len(targets)
        beta = 1 - alpha
        
        self.score = (1 - alpha * self.FN / N_normal - beta * self.FP / N_fault) * 100
    
    def print(self):
        logger = get_logger()
        data = {
            ' ': ['实际正常', '实际结冰'],
            '预测正常': [f'{self.TP}(TP)', f'{self.FP}(FP)'],
            '预测结冰': [f'{self.FN}(FN)', f'{self.TN}(TN)'],
            '  ': [' ', ' ']
        }
        df = pd.DataFrame.from_dict(data)
        logger.info(f'Metrics: \n'
                    f'Confusion Matrix:\n{df.to_string(index=False)}\n'
                    f'Classification Report:\n{self.cr}\n'
                    f'MCC%: {self.MCC * 100}\n'
                    f'Score: {self.score}')

class CustomJSONEncoder(json.JSONEncoder):
    def default(self, obj):
        try:
            # 对象无法序列化时，尝试用 str() 表示
            return str(obj)
        except Exception as e:
            # 如果 str(obj) 也失败，可以返回错误提示
            return f"Unserializable object of type {type(obj).__name__}: {e}"